using DataAnalyticsTools.Models;
using System;
using System.Collections.Generic;
using System.Linq;

namespace DataAnalyticsTools.Core.ChangePointDetect
{
    /// <summary>
    /// 波动率变化点检测器
    /// 通过检测标准差（波动率）的变化来识别数据波动性改变的位置
    /// </summary>
    public static class VolatilityDetector
    {
        /// <summary>
        /// 最大置信度上限
        /// </summary>
        private const float MAX_CONFIDENCE = 0.95f;

        /// <summary>
        /// 使用波动率方法检测变化点
        /// </summary>
        /// <param name="values">待检测的时间序列数据</param>
        /// <param name="windowSize">滑动窗口大小</param>
        /// <param name="sensitivityFactor">灵敏度因子（默认1.5，建议范围1.0-3.0）
        /// - 值越小: 更敏感，检测更多变化点
        /// - 值越大: 更保守，只检测显著变化
        /// 公式: threshold = sensitivityFactor (波动率比率阈值)
        /// 注: 波动率比率 = max(std1/std2, std2/std1)，比率越大表示波动性变化越显著</param>
        /// <returns>检测到的变化点列表</returns>
        public static List<ChangePoint> Detect(float[] values, int windowSize, float sensitivityFactor = 1.5f)
        {
            var changePoints = new List<ChangePoint>();

            // 需要至少 2*windowSize + 1 个点才能进行波动率检测
            if (values.Length < windowSize * 2 + 1)
                return changePoints;

            for (int i = windowSize; i < values.Length - windowSize; i++)
            {
                // 左窗口：变化点之前的windowSize个点
                var leftWindow = values.Skip(i - windowSize).Take(windowSize).ToArray();
                // 右窗口：从变化点开始的windowSize个点
                var rightWindow = values.Skip(i).Take(windowSize).ToArray();

                // 计算两个窗口的波动率（标准差）
                float leftVolatility = MathHelper.CalculateStdDev(leftWindow);
                float rightVolatility = MathHelper.CalculateStdDev(rightWindow);

                // 计算波动率比率（总是 >= 1）
                // 较大的比率表示波动率有显著变化
                float volatilityRatio = Math.Max(
                    leftVolatility / (rightVolatility + MathHelper.MIN_STD_DEV),
                    rightVolatility / (leftVolatility + MathHelper.MIN_STD_DEV));

                // 检查是否超过阈值
                // sensitivityFactor 直接作为阈值：值越大越不敏感
                if (volatilityRatio > sensitivityFactor)
                {
                    // 判断波动率变化方向
                    TrendDirection changeType = rightVolatility > leftVolatility
                        ? TrendDirection.Increasing  // 波动率增加
                        : TrendDirection.Decreasing; // 波动率降低

                    // 计算置信度：使用F检验的思想
                    // volatilityRatio类似于F统计量（方差比）

                    // 1. 统计显著性：ratio相对于阈值的比率
                    float statisticalSignificance = volatilityRatio / sensitivityFactor;

                    // 2. 效应大小：波动率变化的对数比
                    // 使用对数变换使得增加和减少对称：log(2) = -log(0.5)
                    // 对数比 > log(2) (ratio > 2.0) 表示显著的波动率变化
                    float logRatio = (float)Math.Log(volatilityRatio);
                    float effectSize = Math.Abs(logRatio) / (float)Math.Log(2); // 归一化：log(2)作为单位

                    // 3. 综合置信度：使用几何平均数
                    // 确保波动率变化既要统计显著，又要有实际意义
                    float rawConfidence = (float)Math.Sqrt(statisticalSignificance * Math.Min(effectSize, 1.0f));

                    // 4. 映射到[0, MAX_CONFIDENCE]区间
                    float confidence = Math.Min(MAX_CONFIDENCE, 1.0f - (float)Math.Exp(-rawConfidence));

                    changePoints.Add(new ChangePoint(
                        index: i,
                        score: volatilityRatio,
                        changeType: changeType,
                        magnitude: Math.Abs(rightVolatility - leftVolatility),
                        confidence: confidence
                    ));
                }
            }

            return changePoints;
        }

    }
}
